﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace Hack.Forms
{
    public class ShakeForm : Form
    {
        const int WM_NCMOUSEMOVE = 0xA0;
        const int WM_NCLBUTTONDOWN = 0xA1;
        const int WM_NCRBUTTONDOWN = 0xA4;
        const int WM_WINDOWPOSCHANGED = 0x0047;

        private bool captureMovement;
        private DateTime captureStart;
        private DateTime captureEnd;
        private List<Point> movements;

        public event EventHandler FormShaken;

        protected override void WndProc(ref Message m)
        {
            //Set primary mouse button
            int mouseButton = SystemInformation.MouseButtonsSwapped ? WM_NCRBUTTONDOWN : WM_NCLBUTTONDOWN;

            if (m.Msg == mouseButton) //Mouse Down
            {
                //Start capturing window movements
                captureStart = DateTime.Now;
                captureMovement = true;
            }
            else if (captureMovement && m.Msg == WM_NCMOUSEMOVE) //Left Mouse Up
            {
                //Stop capturing window movements
                captureEnd = DateTime.Now;
                captureMovement = false;

                //Process movement data
                ProcessMovement();
            }

            if (m.Msg == WM_WINDOWPOSCHANGED)
            {
                if (captureMovement)
                {
                    //Initialize movement list
                    if (movements == null)
                        movements = new List<Point>();

                    //Store current position
                    movements.Add(new Point(this.Left, this.Top));
                }
            }

            base.WndProc(ref m);
        }

        private void ProcessMovement()
        {
            if (this.WasShaken() && FormShaken != null)
                FormShaken(this, EventArgs.Empty);

            //Reset Markers
            movements.Clear();
            movements = null;
            captureStart = DateTime.MinValue;
            captureEnd = DateTime.MinValue;
        }

        private bool WasShaken()
        {
            if (movements != null)
            {
                //Calculate average point from all the collected positions
                Point avg = GetAveragePoint(movements);

                //Calculate difference of average point to current position
                Point deltaPoint = new Point();
                deltaPoint.X = this.Left - avg.X;
                deltaPoint.Y = this.Top - avg.Y;

                //Calculate the number of milliseconds that spanned while the window moved
                //Note: Only uses seconds and milliseconds
                TimeSpan movementTime = captureEnd.Subtract(captureStart);
                int msSpan = (movementTime.Seconds * 1000 + movementTime.Milliseconds);

                //If values fall within a certain range, then the window was shaken
                return msSpan <= 1000 && //speed of the shake in milliseconds
                       movements.Count >= 20 && //amount of movements in the shake
                       System.Math.Abs(deltaPoint.X) <= 40 && System.Math.Abs(deltaPoint.Y) <= 40; //average "size" of shake
            }

            return false;
        }

        private Point GetAveragePoint(List<Point> points)
        {
            Point avg = new Point();
            foreach (Point p in points)
            {
                avg.X += p.X;
                avg.Y += p.Y;
            }

            avg.X /= points.Count;
            avg.Y /= points.Count;

            return avg;
        }
    }

}
